﻿interface IElemStyleInfo {
    element: HTMLElement;
    computedStyle: CSSStyleDeclaration;
    widthWithoutBorder: number;
    heightWithoutBorder: number;
    borderLeftWidth: number;
    borderTopWidth: number;
    borderRightWidth: number;
    borderBottomWidth: number;
    marginLeft: number;
    marginTop: number;
    width: number;
    height: number;
    left: number;
    right: number;
    top: number;
    bottom: number;
    lineHeight: number;
}

function GetElementStyleInfo(elem: HTMLElement) {
    var computedStyle = document.defaultView.getComputedStyle(elem);
    var res = <IElemStyleInfo>{
        element: elem,
        computedStyle: computedStyle,
        widthWithoutBorder: parseFloat(computedStyle.getPropertyValue("width")),
        heightWithoutBorder: parseFloat(computedStyle.getPropertyValue("height")),
        borderLeftWidth: parseFloat(computedStyle.getPropertyValue("border-left-width")),
        borderTopWidth: parseFloat(computedStyle.getPropertyValue("border-top-width")),
        borderRightWidth: parseFloat(computedStyle.getPropertyValue("border-right-width")),
        borderBottomWidth: parseFloat(computedStyle.getPropertyValue("border-bottom-width")),
        marginLeft: parseFloat(computedStyle.getPropertyValue("margin-left")),
        marginTop: parseFloat(computedStyle.getPropertyValue("margin-top")),
        lineHeight: parseFloat(computedStyle.getPropertyValue("line-height"))
    };

    res.width = res.widthWithoutBorder + res.borderLeftWidth + res.borderRightWidth;
    res.height = res.heightWithoutBorder + res.borderTopWidth + res.borderBottomWidth;

    res.left = parseFloat(computedStyle.getPropertyValue("left")) + res.marginLeft;
    res.top = parseFloat(computedStyle.getPropertyValue("top")) + res.marginTop;
    res.right = res.left + res.width;
    res.bottom = res.top + res.height;

    return res;
}

function ScrollToPage(elem: HTMLElement, page: number) {
    var elemStyle = GetElementStyleInfo(elem);
    var elemScrollHeight = elem.scrollHeight;
    var elemHeight = elemStyle.height;
    var lineHeight = elemStyle.lineHeight;
    if (isNaN(lineHeight)) {
        // Hack for Webkit browsers...
        console.log("Hacking");
        var newLine = document.createElement("span");
        newLine.textContent = "\nHACK";
        newLine.style.visibility = "hidden";
        
        elem.appendChild(newLine);
        lineHeight = elemScrollHeight - elem.scrollHeight;
        elem.removeChild(newLine);
    }
    var pageHeight = Math.floor(elemHeight / lineHeight) * lineHeight;
    elem.scrollTop = page * pageHeight;
}

/**
 * Performs a binary search on the host array.
 */
function binaryIndexOf<T>(array: { [index: number]: T; length: number }, compare: (currentElement: T) => number) {
    'use strict';

    var minIndex = 0;
    var maxIndex = array.length - 1;
    var currentIndex: number;
    var currentElement: T;

    while (minIndex <= maxIndex) {
        currentIndex = (minIndex + maxIndex) / 2 | 0;
        currentElement = array[currentIndex];

        var tmp = compare(currentElement);
        if (tmp < 0) {
            minIndex = currentIndex + 1;
        } else if (tmp > 0) {
            maxIndex = currentIndex - 1;
        } else {
            return currentIndex;
        }
    }

    return maxIndex;
}

function easingPattern(easingType: string, percentage: number) {
    var pattern: number;
    switch (easingType) {
        case 'easeInQuad':
            pattern = percentage * percentage; // accelerating from zero velocity
            break;
        case 'easeOutQuad':
            pattern = percentage * (2 - percentage); // decelerating to zero velocity
            break;
        case 'easeInOutQuad':
            pattern = percentage < 0.5 ? 2 * percentage * percentage : -1 + (4 - 2 * percentage) * percentage; // acceleration until halfway, then deceleration
            break;
        case 'easeInCubic':
            pattern = percentage * percentage * percentage; // accelerating from zero velocity
            break;
        case 'easeOutCubic':
            pattern = (--percentage) * percentage * percentage + 1; // decelerating to zero velocity
            break;
        case 'easeInOutCubic':
            pattern = percentage < 0.5 ? 4 * percentage * percentage * percentage : (percentage - 1) * (2 * percentage - 2) * (2 * percentage - 2) + 1; // acceleration until halfway, then deceleration
            break;
        case 'easeInQuart':
            pattern = percentage * percentage * percentage * percentage; // accelerating from zero velocity
            break;
        case 'easeOutQuart':
            pattern = 1 - (--percentage) * percentage * percentage * percentage; // decelerating to zero velocity
            break;
        case 'easeInOutQuart':
            pattern = percentage < 0.5 ? 8 * percentage * percentage * percentage * percentage : 1 - 8 * (--percentage) * percentage * percentage * percentage; // acceleration until halfway, then deceleration
            break;
        case 'easeInQuint':
            pattern = percentage * percentage * percentage * percentage * percentage; // accelerating from zero velocity
            break;
        case 'easeOutQuint':
            pattern = 1 + (--percentage) * percentage * percentage * percentage * percentage; // decelerating to zero velocity
            break;
        case 'easeInOutQuint':
            pattern = percentage < 0.5 ? 16 * percentage * percentage * percentage * percentage * percentage : 1 + 16 * (--percentage) * percentage * percentage * percentage * percentage; // acceleration until halfway, then deceleration
            break;
    }
    return pattern || percentage; // no easing, no acceleration
};

function scrollToPosition(elem: HTMLElement, pos: number, duration: number = 500, easingType?: string) {
    var start: number = null, startPos = elem.scrollTop;
    var length = pos - startPos;
    var step = (time: number) => {
        if (start === null) {
            start = time;
            requestAnimationFrame(step);
        } else {
            var percentage = (time - start) / duration;
            if (percentage >= 1) {
                elem.scrollTop = pos;
            } else {
                elem.scrollTop = startPos + easingPattern(easingType, percentage) * length;
                requestAnimationFrame(step);
            }
        }
    };
    requestAnimationFrame(step);
}

function scrollToNextPage(elem: HTMLUListElement) {
    var target = elem.scrollTop + elem.clientHeight;
    if (target + 2 > elem.scrollHeight) {
        scrollToPosition(elem, 0, 1000, "easeInOutQuint");
    } else {
        var idx = binaryIndexOf(elem.children, (currentElement: HTMLLIElement) => {
            return currentElement.offsetTop - target;
        });
        target = (<HTMLLIElement>elem.children[idx]).offsetTop;
        if (target > elem.scrollHeight - elem.clientHeight) {
            scrollToPosition(elem, elem.scrollHeight - elem.clientHeight, 1000, "easeInOutQuart");
        } else {
            scrollToPosition(elem, target, 1000, "easeInOutCubic");
        }
    }
}

function setAutoScrollPage(elem: HTMLUListElement, interval: number = 1000 * 15) {
    if (elem.scrollHeight > elem.clientHeight) {
        setInterval(function () {
            scrollToNextPage(elem);
        }, interval);
    }
}